msg_tool\scripts\softpal\scr/
mod.rs1mod disasm;
3
4use crate::ext::io::*;
5use crate::scripts::base::*;
6use crate::types::*;
7use crate::utils::encoding::*;
8use anyhow::Result;
9use disasm::*;
10use std::collections::HashMap;
11use std::io::{Read, Write};
12
13#[derive(Debug)]
14pub struct SoftpalScriptBuilder {}
16
17impl SoftpalScriptBuilder {
18 pub fn new() -> Self {
20 Self {}
21 }
22}
23
24impl ScriptBuilder for SoftpalScriptBuilder {
25 fn default_encoding(&self) -> Encoding {
26 Encoding::Cp932
27 }
28
29 fn build_script(
30 &self,
31 buf: Vec<u8>,
32 filename: &str,
33 encoding: Encoding,
34 _archive_encoding: Encoding,
35 config: &ExtraConfig,
36 archive: Option<&Box<dyn Script>>,
37 ) -> Result<Box<dyn Script>> {
38 Ok(Box::new(SoftpalScript::new(
39 buf, filename, encoding, config, archive,
40 )?))
41 }
42
43 fn extensions(&self) -> &'static [&'static str] {
44 &["src"]
45 }
46
47 fn script_type(&self) -> &'static ScriptType {
48 &ScriptType::Softpal
49 }
50
51 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
52 if buf_len >= 4 && buf.starts_with(b"Sv20") {
53 return Some(10);
54 }
55 None
56 }
57}
58
59#[derive(Debug)]
60pub struct SoftpalScript {
62 data: MemReader,
63 strs: Vec<PalString>,
64 texts: MemReader,
65 encoding: Encoding,
66 label_offsets: Vec<u32>,
67 add_message_index: bool,
68}
69
70impl SoftpalScript {
71 pub fn new(
73 buf: Vec<u8>,
74 filename: &str,
75 encoding: Encoding,
76 config: &ExtraConfig,
77 archive: Option<&Box<dyn Script>>,
78 ) -> Result<Self> {
79 let texts = Self::load_texts_data(Self::load_file(filename, archive, "TEXT.DAT")?)?;
80 let points_data = MemReader::new(Self::load_file(filename, archive, "POINT.DAT")?);
81 let label_offsets = Self::load_point_data(points_data)?;
82 let strs = Disasm::new(&buf, &label_offsets)?.disassemble::<MemWriter>(None)?;
83 Ok(Self {
84 data: MemReader::new(buf),
85 strs,
86 encoding,
87 texts,
88 label_offsets,
89 add_message_index: config.softpal_add_message_index,
90 })
91 }
92
93 fn load_file(filename: &str, archive: Option<&Box<dyn Script>>, name: &str) -> Result<Vec<u8>> {
94 if let Some(archive) = archive {
95 Ok(archive
96 .open_file_by_name(name, true)
97 .map_err(|e| anyhow::anyhow!("Failed to open file {} in archive: {}", name, e))?
98 .data()?)
99 } else {
100 let mut path = std::path::PathBuf::from(filename);
101 path.set_file_name(name);
102 std::fs::read(path).map_err(|e| anyhow::anyhow!("Failed to read file {}: {}", name, e))
103 }
104 }
105
106 fn load_texts_data(data: Vec<u8>) -> Result<MemReader> {
107 let mut writer = MemWriter::from_vec(data);
108 if writer.data.len() >= 0x14 {
109 let ind = writer.cpeek_u32_at(0x10)?;
110 writer.pos = 0x10;
111 if ind != 0 {
112 let mut shift = 4;
113 for _ in 0..(writer.data.len() / 4 - 4) {
114 let mut data = writer.cpeek_u32()?;
115 let mut add = data.to_le_bytes();
116 add[0] = add[0].rotate_left(shift);
117 shift = (shift + 1) % 8;
118 data = u32::from_le_bytes(add);
119 data ^= 0x084DF873 ^ 0xFF987DEE;
120 writer.write_u32(data)?;
121 }
122 }
123 }
124 Ok(MemReader::new(writer.into_inner()))
125 }
126
127 fn load_point_data(mut data: MemReader) -> Result<Vec<u32>> {
128 let mut magic = [0u8; 16];
129 data.read_exact(&mut magic)?;
130 if magic != *b"$POINT_LIST_****" {
131 return Err(anyhow::anyhow!("Invalid point list magic: {:?}", magic));
132 }
133 let mut label_offsets = Vec::new();
134 while !data.is_eof() {
135 label_offsets.push(data.read_u32()? + CODE_OFFSET);
136 }
137 label_offsets.reverse();
138 Ok(label_offsets)
139 }
140}
141
142impl Script for SoftpalScript {
143 fn default_output_script_type(&self) -> OutputScriptType {
144 OutputScriptType::Json
145 }
146
147 fn default_format_type(&self) -> FormatOptions {
148 FormatOptions::None
149 }
150
151 fn is_output_supported(&self, _: OutputScriptType) -> bool {
152 true
153 }
154
155 fn multiple_message_files(&self) -> bool {
156 true
157 }
158
159 fn extract_messages(&self) -> Result<Vec<Message>> {
160 let mut messages = Vec::new();
161 let mut name = None;
162 let max_len = self.texts.data.len() as u32;
163 for str in &self.strs {
164 let addr = self.data.cpeek_u32_at(str.offset as u64)?;
165 if addr - 4 > max_len {
166 continue;
167 }
168 let idx = self.texts.cpeek_u32_at(addr as u64)?;
169 let text = self.texts.cpeek_cstring_at(addr as u64 + 4)?;
170 let text =
171 decode_to_string(self.encoding, text.as_bytes(), false)?.replace("<br>", "\n");
172 let text = if self.add_message_index {
173 format!("[{}]{}", idx, text)
174 } else {
175 text
176 };
177 match str.typ {
178 StringType::Name => {
179 if text.is_empty() {
180 continue; }
182 name = Some(text);
183 }
184 StringType::Message => messages.push(Message {
185 name: name.take(),
186 message: text,
187 }),
188 StringType::Hover => messages.push(Message::new(text, None)),
189 StringType::Label => {} }
191 }
192 Ok(messages)
193 }
194
195 fn extract_multiple_messages(&self) -> Result<HashMap<String, Vec<Message>>> {
196 let mut hovers = Vec::new();
197 let mut messages = Vec::new();
198 let mut label = None;
199 let mut name = None;
200 let mut result = HashMap::new();
201 let max_len = self.texts.data.len() as u32;
202 for str in &self.strs {
203 let addr = self.data.cpeek_u32_at(str.offset as u64)?;
204 if addr - 4 > max_len {
205 continue;
206 }
207 let idx = self.texts.cpeek_u32_at(addr as u64)?;
208 let text = self.texts.cpeek_cstring_at(addr as u64 + 4)?;
209 let ptext =
210 decode_to_string(self.encoding, text.as_bytes(), false)?.replace("<br>", "\n");
211 let text = if self.add_message_index {
212 format!("[{}]{}", idx, ptext)
213 } else {
214 ptext.clone()
215 };
216 match str.typ {
217 StringType::Name => {
218 if text.is_empty() {
219 continue; }
221 name = Some(text);
222 }
223 StringType::Message => messages.push(Message::new(text, name.take())),
224 StringType::Hover => hovers.push(Message::new(text, None)),
225 StringType::Label => {
226 if !messages.is_empty() {
227 let key = label.take().unwrap_or_else(|| "default".to_string());
228 if result.contains_key(&key) {
229 eprintln!(
230 "Warning: Duplicate label '{}', overwriting previous messages.",
231 key
232 );
233 crate::COUNTER.inc_warning();
234 }
235 result.insert(key, messages);
236 messages = Vec::new();
237 }
238 label = Some(ptext);
239 }
240 }
241 }
242 if !messages.is_empty() {
243 let key = label.take().unwrap_or_else(|| "default".to_string());
244 result.insert(key, messages);
245 }
246 if !hovers.is_empty() {
247 result.insert("hover".to_string(), hovers);
248 }
249 Ok(result)
250 }
251
252 fn import_messages<'a>(
253 &'a self,
254 messages: Vec<Message>,
255 mut file: Box<dyn WriteSeek + 'a>,
256 filename: &str,
257 encoding: Encoding,
258 replacement: Option<&'a ReplacementTable>,
259 ) -> Result<()> {
260 let mut texts_filename = std::path::PathBuf::from(filename);
261 texts_filename.set_file_name("TEXT.DAT");
262 let mut texts = Vec::new();
263 let mut reader = self.texts.to_ref();
264 reader.pos = 0x10;
265 while !reader.is_eof() {
266 reader.pos += 4; texts.push(reader.read_cstring()?)
268 }
269 let mut texts_file = std::fs::File::create(&texts_filename)
270 .map_err(|e| anyhow::anyhow!("Failed to create TEXT.DAT file: {}", e))?;
271 file.write_all(&self.data.data)?;
272 let mut mes = messages.iter();
273 let mut mess = mes.next();
274 let texts_data_len = self.texts.data.len() as u32;
275 let mut num_offset_map: HashMap<u32, u32> = HashMap::new();
276 for str in &self.strs {
277 let addr = self.data.cpeek_u32_at(str.offset as u64)?;
278 if addr + 4 > texts_data_len {
279 continue;
280 }
281 if str.typ.is_label() {
282 continue; }
284 let m = match mess {
285 Some(m) => m,
286 None => return Err(anyhow::anyhow!("Not enough messages.")),
287 };
288 let mut text = match str.typ {
289 StringType::Name => match &m.name {
290 Some(name) => name.clone(),
291 None => return Err(anyhow::anyhow!("Missing name for message.")),
292 },
293 StringType::Message => {
294 let m = m.message.clone();
295 mess = mes.next();
296 m
297 }
298 StringType::Hover => {
299 let m = m.message.clone();
300 mess = mes.next();
301 m
302 }
303 StringType::Label => continue, };
305 if let Some(repl) = replacement {
306 for (from, to) in repl.map.iter() {
307 text = text.replace(from, to);
308 }
309 }
310 text = text.replace("\n", "<br>");
311 let encoded = encode_string(encoding, &text, false)?;
312 let s = std::ffi::CString::new(encoded)?;
313 let num = texts.len() as u32;
314 num_offset_map.insert(num, str.offset);
315 texts.push(s);
316 }
317 if mess.is_some() || mes.next().is_some() {
318 return Err(anyhow::anyhow!("Some messages were not processed."));
319 }
320 texts_file.write_all(b"$TEXT_LIST__")?;
321 texts_file.write_u32(texts.len() as u32)?;
322 let mut nf = MemWriter::new();
323 for (num, text) in texts.into_iter().enumerate() {
324 let num = num as u32;
325 let newaddr = nf.pos as u32 + 0x10;
326 if let Some(offset) = num_offset_map.get(&num) {
327 file.write_u32_at(*offset as u64, newaddr)?;
328 }
329 nf.write_u32(num)?;
330 nf.write_cstring(&text)?;
331 }
332 nf.pos = 0;
333 let mut shift = 4;
334 for _ in 0..(nf.data.len() / 4) {
335 let mut data = nf.cpeek_u32()?;
336 data ^= 0x084DF873 ^ 0xFF987DEE;
337 let mut add = data.to_le_bytes();
338 add[0] = add[0].rotate_right(shift);
339 shift = (shift + 1) % 8;
340 data = u32::from_le_bytes(add);
341 nf.write_u32(data)?;
342 }
343 texts_file.write_all(&nf.data)?;
344 Ok(())
345 }
346
347 fn import_multiple_messages<'a>(
348 &'a self,
349 messages: HashMap<String, Vec<Message>>,
350 mut file: Box<dyn WriteSeek + 'a>,
351 filename: &str,
352 encoding: Encoding,
353 replacement: Option<&'a ReplacementTable>,
354 ) -> Result<()> {
355 let mut texts_filename = std::path::PathBuf::from(filename);
356 texts_filename.set_file_name("TEXT.DAT");
357 let mut texts = Vec::new();
358 let mut reader = self.texts.to_ref();
359 reader.pos = 0x10;
360 while !reader.is_eof() {
361 reader.pos += 4; texts.push(reader.read_cstring()?)
363 }
364 let mut texts_file = std::fs::File::create(&texts_filename)
365 .map_err(|e| anyhow::anyhow!("Failed to create TEXT.DAT file: {}", e))?;
366 file.write_all(&self.data.data)?;
367 let hover_messages = messages.get("hover").cloned().unwrap_or_default();
368 let mut hover_iter = hover_messages.iter();
369 let mut hover_mes = hover_iter.next();
370 let mut cur_label: Option<String> = None;
371 let mut cur_messages = messages
372 .get(cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default"))
373 .cloned()
374 .unwrap_or_default();
375 let mut cur_iter = cur_messages.iter();
376 let mut cur_mes = cur_iter.next();
377 let texts_data_len = self.texts.data.len() as u32;
378 let mut num_offset_map: HashMap<u32, u32> = HashMap::new();
379 for str in &self.strs {
380 let addr = self.data.cpeek_u32_at(str.offset as u64)?;
381 if addr + 4 > texts_data_len {
382 continue;
383 }
384 let mut text = match str.typ {
385 StringType::Label => {
386 if cur_mes.is_some() || cur_iter.next().is_some() {
387 return Err(anyhow::anyhow!(
388 "Not all messages were used for label {}.",
389 cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default")
390 ));
391 }
392 let text = self.texts.cpeek_cstring_at(addr as u64 + 4)?;
393 let text = decode_to_string(self.encoding, text.as_bytes(), false)?
394 .replace("<br>", "\n");
395 cur_messages = messages.get(text.as_str()).cloned().unwrap_or_default();
396 cur_iter = cur_messages.iter();
397 cur_mes = cur_iter.next();
398 cur_label = Some(text);
399 continue;
401 }
402 StringType::Hover => {
403 let m = match hover_mes {
404 Some(m) => m,
405 None => return Err(anyhow::anyhow!("Not enough hover messages.")),
406 };
407 let m = m.message.clone();
408 hover_mes = hover_iter.next();
409 m
410 }
411 StringType::Name => {
412 let m = match cur_mes {
413 Some(m) => m,
414 None => {
415 return Err(anyhow::anyhow!(
416 "Not enough messages for label {}.",
417 cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default")
418 ));
419 }
420 };
421 let name = match &m.name {
422 Some(name) => name.clone(),
423 None => return Err(anyhow::anyhow!("Missing name for message.")),
424 };
425 name
426 }
427 StringType::Message => {
428 let m = match cur_mes {
429 Some(m) => m,
430 None => {
431 return Err(anyhow::anyhow!(
432 "Not enough messages for label {}.",
433 cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default")
434 ));
435 }
436 };
437 let m = m.message.clone();
438 cur_mes = cur_iter.next();
439 m
440 }
441 };
442 if let Some(repl) = replacement {
443 for (from, to) in repl.map.iter() {
444 text = text.replace(from, to);
445 }
446 }
447 text = text.replace("\n", "<br>");
448 let encoded = encode_string(encoding, &text, false)?;
449 let s = std::ffi::CString::new(encoded)?;
450 let num = texts.len() as u32;
451 num_offset_map.insert(num, str.offset);
452 texts.push(s);
453 }
454 if cur_mes.is_some() || cur_iter.next().is_some() {
455 return Err(anyhow::anyhow!(
456 "Some messages were not processed for label {}.",
457 cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default")
458 ));
459 }
460 if hover_mes.is_some() || hover_iter.next().is_some() {
461 return Err(anyhow::anyhow!("Some hover messages were not processed."));
462 }
463 texts_file.write_all(b"$TEXT_LIST__")?;
464 texts_file.write_u32(texts.len() as u32)?;
465 let mut nf = MemWriter::new();
466 for (num, text) in texts.into_iter().enumerate() {
467 let num = num as u32;
468 let newaddr = nf.pos as u32 + 0x10;
469 if let Some(offset) = num_offset_map.get(&num) {
470 file.write_u32_at(*offset as u64, newaddr)?;
471 }
472 nf.write_u32(num)?;
473 nf.write_cstring(&text)?;
474 }
475 nf.pos = 0;
476 let mut shift = 4;
477 for _ in 0..(nf.data.len() / 4) {
478 let mut data = nf.cpeek_u32()?;
479 data ^= 0x084DF873 ^ 0xFF987DEE;
480 let mut add = data.to_le_bytes();
481 add[0] = add[0].rotate_right(shift);
482 shift = (shift + 1) % 8;
483 data = u32::from_le_bytes(add);
484 nf.write_u32(data)?;
485 }
486 texts_file.write_all(&nf.data)?;
487 Ok(())
488 }
489
490 fn custom_output_extension<'a>(&'a self) -> &'a str {
491 "txt"
492 }
493
494 fn custom_export(&self, filename: &std::path::Path, _encoding: Encoding) -> Result<()> {
495 let file = std::fs::File::create(filename)
496 .map_err(|e| anyhow::anyhow!("Failed to create file {}: {}", filename.display(), e))?;
497 let mut file = std::io::BufWriter::new(file);
498 Disasm::new(&self.data.data, &self.label_offsets)?.disassemble(Some(&mut file))?;
499 Ok(())
500 }
501}